package travianBot;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Random;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.log4j.Logger;
import org.htmlcleaner.CleanerProperties;
import org.htmlcleaner.HtmlCleaner;
import org.htmlcleaner.PrettyXmlSerializer;
import org.htmlcleaner.TagNode;
import org.htmlcleaner.XPatherException;

import travianBot.botScripts.BuildingScript;
	

public class TravianBot {
	
	static Logger log=Logger.getLogger(TravianBot.class);
	static public boolean THINK_TIME = true;
	static public boolean USE_PROXY = true;
	
	public static final int BUILD_PASSED = 1;
	public static final int BUILD_NOT_READY = 1;
	public static final int	BUILD_REACHED_LEVEL = 5;
	public static final int	BUILD_FAILED = 10;
	
	static String SMTP_SERVER="ukdomappsd10";
	
	Village		currentVillage;
	Account 	account = new Account();
	HttpClient 	httpclient = new HttpClient();
	
	public TravianBot(){
		if(USE_PROXY)
			httpclient.getHostConfiguration().setProxy("www-proxy.computacenter.de", 8081);		
	}
	
	public String login(String username, String password) throws TravianBotException{
		log.info("Logging in");
		
		// call the login page to get the login params
		String httpContents = getHttpContents(LinkMaker.LOGIN_PAGE);
		log.debug(httpContents);
		HashMap<String,String> loginMap = getLoginVariables(httpContents, username, password);				
		
		//login - actually call dorf1
		PostMethod httpPost = new PostMethod(LinkMaker.VILLAGE_PAGE);  
		StringBuilder responseString = new StringBuilder("");
		
		// setup the parameters for login
		for (String key : loginMap.keySet()) {
			httpPost.addParameter(key,loginMap.get(key));
		}
				
		try {
			log.debug("HTTPost to URL:"+httpPost.getURI());
			this.httpclient.executeMethod(httpPost);
			/*
	         * Logic to capture links and optionally store the captured html in a file
	         */
			BufferedReader in = new BufferedReader(new InputStreamReader(httpPost.getResponseBodyAsStream()));
			String line;
	        while(null != (line = in.readLine())){
	        	responseString.append(line+"\n");
	        }
			
		} catch (Exception e) {
			e.printStackTrace();
			throw new TravianBotException(e);			
		}                                                                                 
        
		thinkTimeSleep();
		
		String htmlContent = responseString.toString();
		log.debug(htmlContent);
		
		varifyLogin(htmlContent);		
		
		return htmlContent;
	}
		
	public static HashMap<String,String> getLoginVariables(String htmlContent, String username, String password) throws TravianBotException{
		HashMap<String,String> loginMap = new HashMap<String,String>();				
		//POSTDATA=w=1280%3A1024&login=1254905020&e039146=mike.uk&e92ad4a=*********&e3f27fe=02d7eac3da&s1.x=45&s1.y=12&s1=login		
		
		HtmlCleaner htmlCleaner = new HtmlCleaner();		
		
		try {
			TagNode node = htmlCleaner.clean(htmlContent);
			Object[] nodes = node.evaluateXPath("//form//input");
			
			
			for (int i = 0; i < nodes.length; i++) {
				TagNode inputNode = (TagNode)nodes[i];
				String attributeName = inputNode.getAttributeByName("name");
				String attributeValue = inputNode.getAttributeByName("value");
				String attributeType = inputNode.getAttributeByName("type");
				
				//treat the username and password differently
				if(attributeType.equals("password")){
					loginMap.put(attributeName, password);
				}else if(attributeType.equals("text")){
					loginMap.put(attributeName, username);
				}else{				
					loginMap.put(attributeName, attributeValue);
				}
				log.debug("attributeName:"+attributeName+" attributeValue:"+attributeValue);
			}
			loginMap.put("s1.x", "52");
			loginMap.put("s1.y", "14");
			loginMap.put("s1", "login");
			loginMap.put("w", "1280%3A1024");
			
		} catch (IOException e) {
			log.error("cannot collect the login variables");
			throw new TravianBotException("cannot collect the login variables", e);			
		} catch (XPatherException e) {
			log.error("xpath exception collecting the login variables");
			throw new TravianBotException("xpath exception collecting the login variables", e);	
		}
		return loginMap;
	}
	
	public boolean varifyLogin(String htmlContent) throws TravianBotException{
		// check that it worked
		HtmlCleaner htmlCleaner = new HtmlCleaner();
		try {
			TagNode node = htmlCleaner.clean(htmlContent);
			Object[] nodes = node.evaluateXPath("//div[@id='side_info']/table/thead/tr/td/a");
			//Object[] nodes = node.evaluateXPath("//div[@id='side_info']/table/tr");
			if(nodes == null || nodes.length < 1){
				log.error("problems varifying login, no village list");
				throw new TravianBotException("problems varifying login, no village list");
			}
		} catch (IOException e) {
			e.printStackTrace();
			throw new TravianBotException("problems varifying login", e);
		} catch (XPatherException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new TravianBotException("xpath to varify login is causing a problem", e);
		}
		return true;
	}
	
	public HashMap<String, Village> initVillageList(String pageHTML) throws TravianBotException{
		HashMap<String, Village> villageMap = new HashMap<String, Village>();
		
		HtmlCleaner htmlCleaner = new HtmlCleaner();
		try {
			TagNode node = htmlCleaner.clean(pageHTML);
			CleanerProperties props = htmlCleaner.getProperties();
			PrettyXmlSerializer serializer = new PrettyXmlSerializer(props); 
			//serializer.writeXmlToStream(node, System.out);
			
			// get the side_info div
			try {
				Object[] nodes = node.evaluateXPath("//div[@id='side_info']");
				if(nodes == null || nodes.length == 0){
					log.error("can't find the list of village names");
					throw new TravianBotException("can't find the list of village names");
				}
				TagNode side_infoNode = (TagNode)nodes[0];
				villageMap = account.initVillages(side_infoNode);		
				
			} catch (XPatherException e) {
				log.error("xpath problems finding the list of village names");
				throw new TravianBotException("xpath problems finding the list of village names");
			}
		} catch (Exception e) {
			log.error("Problems initialising the list of village names");
			throw new TravianBotException("Problems initialising the list of village names");
		}	
		
		return villageMap;
	}
		
	
	public boolean upgradeResource(String villageName, String resourceType) throws TravianBotException{
		//load the correct village
		loadVillage(villageName);	
		
		// get the page containing the upgrade link
		ResourceEntry resourceEntry = currentVillage.getLowestLevelResource(resourceType);
		if(resourceEntry == null){
			log.error("unable to find the build link for:"+villageName);
			throw new TravianBotException("unable to find the build link for:"+villageName);
		}
		String upgradeResourcePageLink = LinkMaker.getUpgradeResourcePageLink(resourceEntry);
		boolean returnBoolean = false;
		String htmlContents="";
		try {
			htmlContents = getHttpContents(upgradeResourcePageLink);
		} catch (Exception e) {	
			log.error("unable to fetch the resource upgrade page");
			throw new TravianBotException("unable to fetch the resource upgrade page", e);
		}
		
		// find the upgrade link
		String upgradeLink = getUpgradeLink(htmlContents);		
		
		// call the upgrade link
		if(upgradeLink != null){
			upgradeLink = LinkMaker.completeRelativeLink(upgradeLink);
			log.debug("upgrade link:"+upgradeLink);
			getHttpContents(upgradeLink);
			log.info("Upgrading village:'"+currentVillage.getName()+"' resource:"+resourceType+ " to level:"+resourceEntry.level);
			returnBoolean = true;
		}else{
			log.info("Not ready:'"+currentVillage.getName()+"' to upgrade:"+resourceType+ " clvl:"+resourceEntry.level);
		}
		return returnBoolean;
	}
	
	
	public int upgradeBuilding(String villageName, String buildingName, int maxLevel) throws TravianBotException{
		int returnInt = BUILD_FAILED;
		
		//select the correct village
		Village village = loadVillage(villageName);	
		
		// load the buildings page
		String buildingsPage = getHttpContents(LinkMaker.BUILDINGS_PAGE);
		
		if(village == null){
			log.warn("Village '"+villageName+"' does not exist");
		}else{
			village.initialiseBuildings(buildingsPage);
			
			// get the specific buildings page
			// http://s1.travian.co.uk/build.php?id=29		
			ResourceEntry resourceEntry = village.getBuildingResourceEntry(buildingName);
			
			if(resourceEntry == null){
				log.warn("Creating "+ buildingName+" in village:" +villageName);
				// find a vacant plot
				resourceEntry = village.getBuildingResourceEntry("Building Site");
				if(resourceEntry != null){
					// fetch vacant plot page
					String buildPageLink = LinkMaker.getBuildingPageLink(resourceEntry);
					String htmlContents="";
					try {
						htmlContents = getHttpContents(buildPageLink);
					} catch (Exception e) {	
						log.error("unable to fetch a empty building plot");
						throw new TravianBotException("unable to fetch a empty building plot", e);
					}
					// get the link to create the new build
					String createNewBuildingLink = BuildingScript.getNewBuildLink(htmlContents,buildingName);
					createNewBuildingLink = LinkMaker.completeRelativeLink(createNewBuildingLink);
					// create the new build
					try {
						htmlContents = getHttpContents(createNewBuildingLink);
					} catch (Exception e) {	
						log.error("unable to create new building: "+buildingName+" in village:"+villageName);
						throw new TravianBotException("unable to create new building: "+buildingName+" in village:"+villageName, e);
					}	
					returnInt = BUILD_PASSED;
				}else{
					log.warn("no free plots left to create: "+buildingName+" in village:"+villageName);
					returnInt = BUILD_REACHED_LEVEL;
				}
			}else if(maxLevel != -1 && resourceEntry.level >= maxLevel){
				returnInt = BUILD_REACHED_LEVEL;
				log.info("Village: '"+villageName+"' building:"+ buildingName+" reached the desired level: "+maxLevel);
			}else{		
				String upgradeResourcePageLink = LinkMaker.getUpgradeResourcePageLink(resourceEntry);
				String htmlContents="";
				try {
					htmlContents = getHttpContents(upgradeResourcePageLink);
				} catch (Exception e) {	
					log.error("unable to fetch the building upgrade page");
					throw new TravianBotException("unable to fetch the building upgrade page", e);
				}
				
				// get the upgrade link
				String upgradeLink = getUpgradeLink(htmlContents);	
				
				//call the upgrade link
				// call the upgrade link
				if(upgradeLink != null){
					upgradeLink = LinkMaker.completeRelativeLink(upgradeLink);
					log.debug("upgrade link:"+upgradeLink);
					getHttpContents(upgradeLink);
					log.info("Upgrading village:'"+currentVillage.getName()+"' building:"+buildingName+ " from level:"+resourceEntry.level);
					returnInt = BUILD_PASSED;
				}else{
					log.info("Not ready for upgrade:'"+currentVillage.getName()+"' building:"+buildingName+ " current level:"+resourceEntry.level);
					returnInt = BUILD_NOT_READY;
				}
			}
		}
		return returnInt;		
	}
	
	public void printVillageDetails(){
		System.out.println(this.account.printVillageCounters());
	}
	
	/**
	 * Call the link to put this village in the session.  Defaults to the resources page.
	 * @param villageName
	 * @throws TravianBotException 
	 * @throws IOException 
	 */
	public Village loadVillage(String villageName) throws TravianBotException {
		//log.info("Loading village: "+villageName);
		Village village = this.account.getVillage(villageName);
		if(village != null){
			String loadVillageLink = LinkMaker.getLoadVillageLink(village);
			String villageResourcesHTML = getHttpContents(loadVillageLink);
			village.initialiseResources(villageResourcesHTML);	
			log.debug(villageResourcesHTML);
			 
			//check we're not under attack
			village.setAttackFlag(areTroopsAttacking(villageResourcesHTML));

		}else{
			log.warn("Village '"+villageName+"'does not exist");
		}
		this.currentVillage = village;
		return village;
	}
	
	/**
	 * Get the link to upgrade a resource, from the HTML page containing it
	 * @param html
	 * @return
	 * @throws TravianBotException 
	 */
	public String getUpgradeLink(String htmlContent) throws TravianBotException{
		HtmlCleaner htmlCleaner = new HtmlCleaner();
		String returnString = null;
		try {
			TagNode node = htmlCleaner.clean(htmlContent);
			Object[] nodeArray = node.evaluateXPath("//p[@id='contract']/a[@class='build']");
			
			if(nodeArray != null && nodeArray.length > 0){
				TagNode anchorNode = (TagNode)nodeArray[0];
				returnString = anchorNode.getAttributeByName("href");								
			}
		} catch (IOException e) {
			e.printStackTrace();
			throw new TravianBotException("problems getting upgrade link", e);
		} catch (XPatherException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new TravianBotException("xpath problems getting upgrade link", e);
		}
		return returnString;
	}
	
	public static boolean areTroopsAttacking(String htmlContent)throws TravianBotException{
		HtmlCleaner htmlCleaner = new HtmlCleaner();
		String returnString = null;
		boolean returnBool = false;
		try {
			TagNode node = htmlCleaner.clean(htmlContent);
			Object[] nodeArray = node.evaluateXPath("//img[@class='att1']");
			//<div id="map_details">
			//Object[] nodeArray = node.evaluateXPath("//td[@class='att1']");
			if(nodeArray != null && nodeArray.length > 0){
				returnBool = true;							
			}
		} catch (IOException e) {
			e.printStackTrace();
			throw new TravianBotException("problems assesing if we're under attack", e);
		} catch (XPatherException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			throw new TravianBotException("xpath problems assesing if we're under attack", e);
		}
		return returnBool;
	}
	
	private String submitLogin(HashMap<String,String> paramMap, String url){
		PostMethod httpPost = new PostMethod(url);  
		StringBuilder responseString = new StringBuilder("");
		
		// setup the parameters for login
		for (String key : paramMap.keySet()) {
			httpPost.addParameter(key,paramMap.get(key));
		}
				
		try {
			this.httpclient.executeMethod(httpPost);
			/*
	         * Logic to capture links and optionally store the captured html in a file
	         */
			BufferedReader in = new BufferedReader(new InputStreamReader(httpPost.getResponseBodyAsStream()));
			String line;
	        while(null != (line = in.readLine())){
	        	responseString.append(line+"\n");
	        }
			
		} catch (HttpException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}                                                                                 
        
        return responseString.toString();                           
	}
			
	/**
	 * load any villages which haven't already been loaded
	 * @throws TravianBotException 
	 */
	public void loadVillagesWhichArentLoaded() throws TravianBotException{
		ArrayList<Village> villageList = this.account.getVillagesWhichArentLoaded();
		for (Village village : villageList) {
			loadVillage(village.getName());	
		}
	}
		
	public void allertOnAttacks() throws TravianBotException{
		//ensure all villages have been loaded
		loadVillagesWhichArentLoaded();
		
		//see which are under attack
		ArrayList<Village> villageList = this.account.getVillagesUnderAttack();
		
		//send the allert
		StringBuilder villageString = new StringBuilder();		
		for (Village village : villageList) {
			villageString.append(village.getName()+",");
			log.error("***************** "+village.getName()+" is under attack **************************");
		}
		if(villageString.length() > 0){
			String subject = "Under attack: "+villageString.toString();
			subject += " at:"+ new Date();
			TravianBotUtil.sendEmail(SMTP_SERVER, "iain.keddie@computacenter.com,iain.keddie@gmail.com", "travianbot@anon.com", subject, subject);
		}
	}
	
	public static void thinkTimeSleep(){
		Random generator = new Random();
		int sleepSeconds = ( 3 + generator.nextInt(4) ) * 1000;
		if(THINK_TIME){
			try {
				Thread.sleep(sleepSeconds);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public String getHttpContents(String urlString) throws TravianBotException {
		GetMethod getMethod = new GetMethod(urlString);
		log.debug("calling URL:"+urlString);
		StringBuilder responseString = new StringBuilder("");
		try {
			this.httpclient.executeMethod(getMethod);
			BufferedReader in = new BufferedReader(new InputStreamReader(getMethod.getResponseBodyAsStream()));
			
			String line;
	        while(null != (line = in.readLine())){
	        	responseString.append(line);
	        }
	        
	        thinkTimeSleep();
	        
		} catch (Exception e) {
			log.error(e);
			throw new TravianBotException("unable to connect to url:"+urlString);
		}
		
		return responseString.toString();
	}

	public Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

	public HttpClient getHttpclient() {
		return httpclient;
	}
}
